package modelo.esclavo3;

import java.util.*;
import util.AgregadorRMI;
import util.IPGetter;
import controlador.buzones.BuzonIface;
import controlador.buzones.Buzon;
import controlador.buzones.Mensaje;
import controlador.buzones.MessageTypes;
import controlador.io.Config;

//import org.apache.log4j.Logger;
//import org.apache.log4j.PropertyConfigurator;

/**
 * Clase que representa el automata de la estacion de inspeccion y de las cintas de conjuntos aprobados y rechazados.
 * 
 * @version 1.0
 * @author Globex Inc. 
 */
public class Automata3{
	/**Variable que indica si la cinta de aprobados esta activada*/
	private boolean cintaSalidaActivada; // true en marcha, false parada

	/* Sensores */
  /** Sensor de saturación en la cinta de salida */
	private boolean sensorSaturacionCintaSalida;
  /** Sensor de saturación en la cinta de rechazados */
	private boolean sensorSaturacionCintaRechazo;
  /** Sensor de presencia de un conjunto en la estación de inspección */
	private boolean sensorPresenciaEstacionInspeccion;
  /** Sensor de control de la calidad de los conjuntos soldados */
	private boolean sensorControlCalidad;

	/* Estados */
  /** Booleano de presencia de conjunto en estacion de inspeccion */
	private boolean conjuntoEnEstacion;
  /** Booleano de presencia de conjunto en cinta de salida */
	private boolean conjuntoEnCinta;
  /** Booleano de inspeccion finalizada */
	private boolean conjuntoInspeccionadoFinalizado;
  /** Booleano de conjunto inspeccionado en cinta de salida */
	private boolean conjuntoInspeccionadoEnCintaDeSalida;
  /** Booleano de conjunto inspeccionado en cinta de rechazo */
	private boolean conjuntoInspeccionadoEnCintaDeRechazo;
  /** Booleano del estado de la cinta de salida */
	private int estadoActualCintaSalida;
  /** Booleano del estado de la cinta de rechazo */
	private int estadoActualCintaRechazo;
  /** Booleano del estado de la estación de inspección */
	private int estadoActualEstacionInspeccion;
  /** Booleano del estado del automata */
	private int estadoActualAutomata;

	/**Indica si el automata esta arrancado*/
	private boolean automataArrancado;

  /** Booleano de la parada normal */
	private boolean paradaNormal;
  /** Booleano de la parada de emergencia */
	private boolean paradaEmergencia;

  /** Tiempo en el que se inicia el proceso de inspeccion de un conjunto soldado */
	private long tiempoInicioInspeccion;
  /** Tiempo en el que se termina la inspeccion de un conjunto */
	private long tiempoParadaInspeccion;
  /** Tiempo total de inspeccion de un conjunto */
	private int tiempoDeInspeccion;

  /** Conjuntos que se han soldado e inspeccionado */
	private int conjuntosInspeccionados;
  /** Conjuntos que tras inspeccionarse están correctos */
	private int conjuntosAceptados;
  /** Conjuntos que tras inspeccionarse no están correctos */
	private int conjuntosRechazados;
  /** Capacidad de la cinta de salida */
	private int capacidadSalida;

	/* Posiciones */
  /** 
	private float posicionActualConjunto;
  /** Vector con las posiciones de los conjuntos en la cinta de salida */
	public Vector<Float> posicionesConjuntos;
  /** Vector con los tiempos de los conjuntos en la cinta de salida */
	public Vector<Long> instanteConjuntos;
  /** Variable que controla el instante inicial de un conunto en cinta */
	private long instanteInicialConjunto;

	/* CONFIGURACION */
  /** Ciclo de reloj de ejecución */
	private long cicloReloj; // en milisegundos
  /** Velocidad de la cinta de conjuntos aceptados */
	private float velocidadCintaSalida;// en metros/minuto
  /** Longitud de la cinta de conjuntos aceptados */
	private float longitudCintaSalida;// metros
  /** Longitud de la cinta de conjuntos rechazados */
	private float longitudCintaRechazo;// metros

	private float umbralError;

  /** Buzon de entrada */
	private BuzonIface _miBuzon;
  /** Buzon de salida */
	private BuzonIface _buzonMaestro;

	/** Constructor del Automata 3 que inicializa el valor de todas las variables. */
	//public Automata3(String miID, int miPuerto, String servidorMaestro, int puertoMaestro, String idMaestro) {
	public Automata3() {
		cintaSalidaActivada = false; // true en marcha, false parada
		/* Sensores */
		sensorSaturacionCintaSalida = false;// true ,esta detectando
		sensorSaturacionCintaRechazo = false;// true ,esta detectando
		sensorPresenciaEstacionInspeccion = false;// true ,esta detectando

		/* Suministro*/ 
		conjuntosInspeccionados = 0;
		conjuntosAceptados = 0;
		conjuntosRechazados = 0;

		/* Estados */
		conjuntoEnEstacion = false;
		conjuntoEnCinta = false;
		conjuntoInspeccionadoFinalizado = false;
		conjuntoInspeccionadoEnCintaDeSalida = false;
		conjuntoInspeccionadoEnCintaDeRechazo = false;

		//revisar valores
		estadoActualAutomata = 0;
		estadoActualCintaSalida = 1;
		estadoActualCintaRechazo = 1;
		estadoActualEstacionInspeccion = 1;

		//revisar valores
		tiempoInicioInspeccion = 0;
		tiempoParadaInspeccion = 0;

		posicionesConjuntos = new Vector<Float>();
		instanteConjuntos = new Vector<Long>();
		instanteInicialConjunto = 0;

		//revisar valores
		velocidadCintaSalida=20;
		longitudCintaSalida=10;
		longitudCintaRechazo=10;
		capacidadSalida=50;
		tiempoDeInspeccion=3;

		automataArrancado = false;
		paradaNormal = false;
		paradaEmergencia = false;

		cicloReloj =1000;
		umbralError = 5;

		HashMap<String, String> conf_tmp = Config.read("esclavo3.conf");
		try {
			_miBuzon = new Buzon();
			_miBuzon.registrar(Integer.parseInt(conf_tmp.get("localPort")), conf_tmp.get("localID"));
		} catch (Exception e) {
			e.printStackTrace();
		}

		while(_buzonMaestro == null) {
			try {
				_buzonMaestro = (BuzonIface) AgregadorRMI.obtenerRemoto(conf_tmp.get("masterHost"), Integer.parseInt(conf_tmp.get("masterPort")), conf_tmp.get("masterID"));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		HashMap<String, String> content = new HashMap<String, String>();
		content.put("ip", IPGetter.getIP());
		content.put("puerto", conf_tmp.get("localPort"));
		content.put("id", conf_tmp.get("localID"));
		enviarMensaje(MessageTypes.BEFRIEND, content);
	}

	/**
	 * Metodo que configura los parametros procedentes del automata maestro
	 * 
	 * @param params es el array de parametros
	 */
	private void configurar(Mensaje params) {
		HashMap<String, String> hash = new HashMap<String, String>(); 
		hash = params.get_message();
		/* Parametros de configuracion */
		Set<String> nuevo = hash.keySet();
		for(String str : nuevo) {
			System.out.println(str + " = "+ hash.get(str));
		}
		cicloReloj = Long.parseLong(hash.get("cicloReloj"));
		velocidadCintaSalida = Long.parseLong(hash.get("velocidadCintaOK"));
		longitudCintaSalida = Long.parseLong(hash.get("longitudCintaOK"));
		tiempoDeInspeccion = Integer.parseInt(hash.get("tiempoControl"));
		umbralError = Float.parseFloat(hash.get("umbralError"));

		paradaEmergencia=false;
		paradaNormal=false;
	}

  /** Metodo que resuleve la posición de un conjunto en base al tiempo que ha transcurrido */
	private float obtenerPosicionConjunto() {
		long instanteActual = System.currentTimeMillis();
		//Se calcula el tiempo total del conjunto en la cinta en segundos
		float tiempoTotal = ((instanteActual - instanteInicialConjunto ) / 1000);
		// La velocidad de la cinta esta en metros/minuto
		float posicion = (velocidadCintaSalida * tiempoTotal) / 60;
		// la posicion se calcula a partir de la velocidad de la cinta y el tiempo que ha estado el eje/engranaje en la misma
		if (posicion <= longitudCintaSalida) {
			return posicion;
		} else {
			return longitudCintaSalida;
		}
	}
  
	/**
	 * Metodo que resuleve la posición de un conjunto en base al tiempo que se proporciona
	 *
	 * @param instante Tiempo de inicio del conujunto
	 */
	private float obtenerPosicionConjuntoSalida(long instante) {
		long instanteActual = System.currentTimeMillis();

		//Se calcula el tiempo total del conjunto en la cinta en segundos
		float tiempoTotal = ((instanteActual - instante ) / 1000);
		// La velocidad de la cinta esta en metros/minuto
		float posicion = (velocidadCintaSalida * tiempoTotal) / 60;
		// la posicion se calcula a partir de la velocidad de la cinta y el tiempo que ha estado el conjunto en la misma

		if (posicion <= longitudCintaSalida) {
			return posicion;
		} else {
			return longitudCintaSalida;
		}
	}

	/** Comprueba si el conjunto es detectado por el sensor
	 * 
	 * @param posicionConjunto La posición donde se encuentra el conjunto
	 * @param posicionSensor La posición del sensor
	 * @return indica si el conjunto esta debajo del sensor
	 */
	private boolean estaConjuntoSensor(float posicionConjunto, float posicionSensor) {
		boolean enSensor = false;
		float umbral = (float) (this.longitudCintaSalida * 0.05);
		if ((posicionConjunto >= (posicionSensor - umbral))) {
			enSensor = true;
		}
		return enSensor;
	}

	/**
	 * Devuelve el resultado del control de calidad a partir del umbral de error
	 * indicado por parametro
	 * 
	 * @param error Umbral de error
	 * @return true si el control de calidad es correcto, false en caso contrario
	 */
	public boolean realizarControlCalidad(float error) {
		boolean resultado = false;

		float num = (float)(Math.random());
		System.out.println("Calidad: "+num);

		if(error==0){
			return true;
		}
		if (num < error)
			resultado = false; // Conjunto rechazado
			else
				resultado = true; // Conjunto aprobado

		return resultado;
	}

  /**
	 * Realiza la transición de la estación de inspección por los diferentes estados
   * en el proceso de verificar las soldaduras realizadas
	 *
	 */
	private void transitarEstacionInspeccion(){
		boolean fin = false;

		switch (estadoActualEstacionInspeccion) {
			case 1://conjunto en estacion
				System.out.println("estadoActualEstacionInspeccion "+estadoActualEstacionInspeccion);
				if(!sensorPresenciaEstacionInspeccion){
					conjuntoInspeccionadoFinalizado = false;
					estadoActualEstacionInspeccion=2;
					conjuntoEnEstacion=true;
					sensorPresenciaEstacionInspeccion=true;
					System.out.println("llega un conjunto a la estacion");
				}
				break;
			case 2://conjunto se inspecciona
				System.out.println("estadoActualEstacionInspeccion "+estadoActualEstacionInspeccion);
				tiempoInicioInspeccion = System.currentTimeMillis();
				int tiempoBucleEspera = tiempoDeInspeccion;
				while (tiempoBucleEspera >= 0) {
					try {
						Thread.sleep(cicloReloj);
						tiempoBucleEspera = (int) (tiempoBucleEspera - cicloReloj);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				sensorControlCalidad = realizarControlCalidad(umbralError);
				System.out.println("Conjunto Inspeccionado");
				
				//declaracion del mensaje al automata maestro con notificacion de recogida
				if (sensorControlCalidad){
					// mensaje de conjunto aceptado al maestro robot2 recoja cintaSalida
					conjuntosAceptados++;
					System.out.println("conjuntosAceptados "+conjuntosAceptados);
					enviarMensaje(MessageTypes.ACCEPTED,new HashMap<String,String>());
				}else{
					// mensaje de conjunto rechazado al maestro robot 2 recoja cintaRechazo
					conjuntosRechazados++;
					System.out.println("conjuntosRechazados "+conjuntosRechazados);
					enviarMensaje(MessageTypes.REJECTED,new HashMap<String,String>());
				}
				conjuntoEnCinta = true;
				tiempoParadaInspeccion = System.currentTimeMillis()- tiempoInicioInspeccion;
				estadoActualEstacionInspeccion = 3;
				conjuntoInspeccionadoFinalizado = true;
				cintaSalidaActivada = false;
				break;
			case 3:// se recoge el conjunto montado
				System.out.println("estadoActualEstacionInspeccion "+estadoActualEstacionInspeccion);
				//cuando se ha recibido el mensaje del maestro
				// if decida si hay que añadir el conjunto en la cinta de salida
				if (sensorControlCalidad){
					posicionesConjuntos.add(0f);
					instanteConjuntos.add(System.currentTimeMillis());
					estadoActualCintaSalida = 2;
				}
				else{
					estadoActualCintaRechazo = 2;
				}
				conjuntosInspeccionados++;
				conjuntoEnEstacion = false;
				sensorPresenciaEstacionInspeccion = false;
				System.out.println("Estacion disponible de nuevo");
				System.out.println("Robot 2 deja conjunto inspeccionado");
				//logger.info("robot 2 deja conjunto inspeccionado");
				estadoActualEstacionInspeccion=1;
				cintaSalidaActivada = true;
				break;
		}
	}

  /** Inicializa los valores de las variables  */
	public void inicializar(){
		cintaSalidaActivada = false; // true en marcha, false parada
		/* Sensores */
		sensorSaturacionCintaSalida = false;// true ,esta detectando
		sensorSaturacionCintaRechazo = false;// true ,esta detectando
		sensorPresenciaEstacionInspeccion = false;// true ,esta detectando

		/* Suministro*/ 
		conjuntosInspeccionados = 0;
		conjuntosAceptados = 0;
		conjuntosRechazados = 0;

		/* Estados */
		conjuntoEnEstacion = false;
		conjuntoInspeccionadoFinalizado = false;
		conjuntoInspeccionadoEnCintaDeSalida = false;
		conjuntoInspeccionadoEnCintaDeRechazo = false;

		//revisar valores
		estadoActualAutomata = 0;
		estadoActualCintaSalida = 1;
		estadoActualCintaRechazo = 1;
		estadoActualEstacionInspeccion = 1;

		//revisar valores
		tiempoInicioInspeccion = 0;
		tiempoParadaInspeccion = 0;

		posicionesConjuntos = new Vector<Float>();
		instanteConjuntos = new Vector<Long>();
		instanteInicialConjunto = 0;

		paradaNormal = false;
		paradaEmergencia = false;

	}

  /** Metodo de prueba del funcionamiento del automata */
	public void gestionarPrueba(){
		automataArrancado=true;
		int i=0;
		while(true){
			long instanteInicialAutomata = System.currentTimeMillis();
			if (automataArrancado) {
				if (!paradaEmergencia) {
					transitarEstacionInspeccion();
				}       
			}
			long instanteFinalAutomata = System.currentTimeMillis();
			long tiempoEsperaAUT3 = cicloReloj - (instanteFinalAutomata - instanteInicialAutomata);

			try {
				if (tiempoEsperaAUT3 >= 0)
					Thread.sleep(tiempoEsperaAUT3);
				else
					System.out.println("El sistema tiene un ciclo de reloj muy bajo");
			} catch (InterruptedException e) {
				System.out.println("Se ha producido un error al dormir el Automata3 "   + e);
				e.printStackTrace();
			}
			i++;
		}
	}

	/**
   * Gestiona toda la funcionalidad del automata 3 recibiendo las ordenes de ejecucion
	 * del automata maestro.
	 */
	public void gestionar() {
		long instanteInicialAutomata, instanteFinalAutomata, tiempoDormir;
		Mensaje mensajeBuzonAUT3 = null;
		boolean fin = false;

		while (true) {
			instanteInicialAutomata = System.currentTimeMillis();
			try{
				mensajeBuzonAUT3 = (Mensaje) _miBuzon.desencolar();
			}catch(Exception e){
				System.out.println(e);
			}
			if (mensajeBuzonAUT3 != null) {
				while (mensajeBuzonAUT3 != null) {
					MessageTypes mess = mensajeBuzonAUT3.get_type();
					switch (mess) {
						case START:
							//Arrancar
							automataArrancado = true;
							this.inicializar();
							configurar(mensajeBuzonAUT3);
							System.out.println("START");
							break;
						case STOP:
							// Parada normal
							paradaNormal = true;
							System.out.println("STOP");
							break;
						case EMERGENCY_STOP:
							// Parada emergencia
							paradaEmergencia = true;
							System.out.println("EMERGENCY STOP");
							break;
						case INSPECTION:        
							// El robot 2 ha dejado un conjunto en la estacion de inspeccion
							if (estadoActualEstacionInspeccion == 1){
								transitarEstacionInspeccion();
								System.out.println("INSPECTION");
								transitarEstacionInspeccion();
							}
							break;
						case EXIT: // Conjunto puesto en cinta de salida
							if (estadoActualEstacionInspeccion == 3){
								transitarEstacionInspeccion();
							}System.out.println("EXIT");
							break;
						default:
							break;
					} 
					try{
						mensajeBuzonAUT3 = (Mensaje) _miBuzon.desencolar();
					}catch(Exception e){
						System.out.println(e);
					}
				}// fin del while
			}else {
				System.out.println("No hay ningun mensaje en el buzon de entrada");
			}
			if (automataArrancado) {
				if (!paradaEmergencia) {
					if ((conjuntoEnCinta) && cintaSalidaActivada) {
						for(int i=0;i<posicionesConjuntos.size();i++){
							posicionesConjuntos.set(i,obtenerPosicionConjuntoSalida(instanteConjuntos.get(i)));
						}
						for(int i=0;i<posicionesConjuntos.size() && !fin;i++){
							if(posicionesConjuntos.elementAt(0) == longitudCintaSalida){
								posicionesConjuntos.remove(0);
								instanteConjuntos.remove(0);
							}else
								fin = true;
						}
						if (posicionesConjuntos.size() == 0){
							conjuntoEnCinta = false;
							estadoActualCintaSalida = 1;
						}

					}
					HashMap<String, String> estados=new HashMap<String, String>();
					estados.put("estadoEstacionInspeccion", Integer.toString(estadoActualEstacionInspeccion));
					System.out.println("estadoEstacionInspeccion: "+estadoActualEstacionInspeccion);
					
					estados.put("conjuntosAceptados", Integer.toString(conjuntosAceptados));
					System.out.println("conjuntosAceptados: "+conjuntosAceptados);
					
					estados.put("conjuntosRechazados", Integer.toString(conjuntosRechazados));
					System.out.println("conjuntosRechazados: "+conjuntosRechazados);
					
					estados.put("conjuntosCintaSalida", Integer.toString(posicionesConjuntos.size()));
					System.out.println("conjuntosCintaSalida: "+posicionesConjuntos.size());
					
					estados.put("estadoCintaSalida", Integer.toString(estadoActualCintaSalida));
					System.out.println("estadoCintaSalida: "+estadoActualCintaSalida);

					enviarMensaje(MessageTypes.ESTADOS3,estados);
					System.out.println("enviando mensaje...");
				}else{
					System.out.println("Esperando mensaje de activación");
				}
			}
			instanteFinalAutomata = System.currentTimeMillis();
			tiempoDormir = cicloReloj - (instanteFinalAutomata - instanteInicialAutomata);

			try {
				if (tiempoDormir >= 0)
					Thread.sleep(tiempoDormir);
			} catch (InterruptedException e) {
				System.out.println("Error al dormir el automata esclavo 3"+e);
			}
		}
	}

	/** Metodo que envia los mensajes al automata maestro */
	private void enviarMensaje(MessageTypes type, HashMap<String, String> content) {
		Mensaje msj = new Mensaje();
		msj.set_type(type);
		msj.set_source("esclavo3");
		msj.set_target("master");
		msj.set_message(content);  
		try{
			_buzonMaestro.encolar(msj);
		} catch (Exception e) {
			// TODO: handle exception
		}
	}

  /** Metodo de ejecución del automata 3 */
	public static void main(String[] args){
		//Automata3 automata=new Automata3("esclavo3",1099,"163.117.142.60",1099,"master");
		Automata3 automata=new Automata3();
		//automata.gestionarPrueba();
		automata.gestionar();
	}
}


